# SERVER TWITTER
# --------------------------------------------------------------------------
# Handles communications with Twitter, but needs valid credentials set
# on the [settings](settings.html).

class Twitter

    # Require modules.
    database = require "./database.coffee"
    dataParser = require "./dataParser.coffee"
    logger = require "./logger.coffee"
    moment = require "moment"
    settings = require "./settings.coffee"
    twitter = require "ntwitter"

    # The twitter handler.
    twit = null

    # Will be true only if it validates with the supplied the credentials.
    auth = false

    # Variables to set when the `checkInbox` and `checkScheduled` ran for the last time,
    # to avoid overrunning these methods.
    lastInboxCheck: moment [2000, 1, 1]
    lastScheduledCheck: moment [2000, 1, 1]


    # INIT
    # --------------------------------------------------------------------------

    # Init the Twitter handler, but only if the consumer and access keys were
    # properly set on the [settings](settings.html).
    init: (retry) =>
        retry = 0 if not retry?

        if settings.Twitter.consumerSecret? and settings.Twitter.accessSecret? and settings.Twitter.accessSecret isnt ""
            keys =
                consumer_key: settings.Twitter.consumerKey,
                consumer_secret: settings.Twitter.consumerSecret,
                access_token_key: settings.Twitter.accessToken,
                access_token_secret: settings.Twitter.accessSecret

            # Create the ntwitter object.
            twit = new twitter keys

            # Verify if credentials are valid and set the `auth` variable.
            twit.verifyCredentials (err, data) =>
                # If fails, log and set a timeout to try again in a few seconds.
                if err?
                    logger.warn "Twitter.init", "Can't verify credentials.", err
                    auth = false
                    setTimeout (() -> @init retry + 1), settings.Twitter.retryInterval * 1000
                else
                    logger.info "Twitter.init", "Authorized with ID #{data.id}."
                    auth = true


    # POST STATUS
    # --------------------------------------------------------------------------

    # Post a single countdown to Twitter, but only if Twitter
    # credentials were validated before, so `auth` must be true.
    postCountdown: (cd) =>
        logTitle = "Twitter.postCountdown"
        if not auth
            @logNoAuth logTitle, "Will NOT post Countdown #{friendlyUrl}."
            return

        # Cancel if no countdown model is passed.
        if not cd?
            if settings.General.debug
                logger.warn logTitle, "Countdown is null or undefined. ABORT!"
            return

        # Cancel if countdown is private.
        if cd.getBool "isPrivate"
            if settings.General.debug
                logger.warn logTitle, "Will NOT post Countdown #{friendlyUrl} to Twitter because it is set as private."
            return

        # Get the its friendly URL.
        friendlyUrl = cd.get "friendlyUrl"

        # Get all necessary details and create the message.
        title = cd.get "title"
        creatorName = cd.get "creatorName"
        targetDate = cd.get "targetDate"
        targetDate = moment targetDate
        message = "#{title} @ #{targetDate.calendar()}, by #{creatorName}."
        link = "http://counting.to/#{friendlyUrl}"

        # Post the status to Twitter.
        @postStatus "#{message} #{link}"

    # Post a status to the Counting.to Twitter timeline. If a callback is passed,
    # it will get triggered with the corresponding status ID, or null if failed.
    postStatus: (message, callback) =>
        logTitle = "Twitter.postStatus"
        if not auth
            @logNoAuth logTitle, "Will NOT post status to Twitter."
            return

        twit.updateStatus message, (err, data) =>
            if err?
                logger.error logTitle, "Post FAILED: #{message}", err
            else if settings.General.debug
                logger.info logTitle, "Posted, ID #{data.id}: #{message}"


    # MESSAGES
    # --------------------------------------------------------------------------

    # Send a direct message to the specified user on Twitter. If a `callback` is
    # specified, it will get triggered passing the sent message ID, or null if failed.
    sendMessage: (message, user, callback) =>
        logTitle = "Twitter.sendMessage"
        if not auth
            @logNoAuth logTitle, "Will NOT send message to #{user}."
            return

        # Send the message to the user.
        twit.sendDirectMessage {"screen_name": user, "text": message}, (err, data) =>
            if err?
                callback null if callback?
                logger.error logTitle, "Send to #{user} FAILED.", message, err
            else
                callback data.id if callback?
                if settings.General.debug
                    logger.info logTitle, "Sent to #{user}.", message

    # Destroy the specified direct message. If a `callback` is specified, it will
    # get triggered passing true or false.
    destroyMessage: (id, callback) =>
        logTitle = "Twitter.destroyMessage"
        if not auth
            @logNoAuth logTitle, "Will NOT destroy message #{id}."
            return

        # Make a request to destroy the specified message.
        twit.destroyDirectMessage id, (err, data) =>
            if err?
                callback false if callback?
                logger.error logTitle, "Destroy #{id} FAILED.", err
            else
                callback true.id if callback?
                if settings.General.debug
                    logger.info logTitle, "Destroy #{id} SUCCESS."


    # AUTOMATED TIMER TASKS
    # --------------------------------------------------------------------------

    # Returns a list of recent direct messages and process them to generate new countdown models.
    # A callback can be passed and will return an error object (if any) and an array with the
    # generated countdowns.
    checkInbox: (callback) =>
        logTitle = "Twitter.checkInbox"
        if not auth
            @logNoAuth logTitle, "Will NOT get recent messages."
            return

        # Abort if last inbox check was just a few moments ago.
        minDate = moment().subtract "s", settings.Twitter.inboxCheckInterval
        if @lastInboxCheck.isAfter minDate
            if settings.General.debug
                logger.warn logTitle, "Inbox checked just a few moments ago: #{@lastInboxCheck.format("HH:mm:ss")}. Abort!"
            return

        @lastInboxCheck = moment()

        # Make a request to get direct messages.
        twit.getDirectMessages {}, (err, data) =>
            if err?
                logger.error logTitle, "Could NOT retrieve direct messages.", err
                callback err, null if callback?
            else
                if settings.General.debug
                    logger.info logTitle, "Will proccess #{data.length} messages."

                result = []

                # Process all messages. If message turns out to be a countdown,
                # destroy it after processing.
                for msg in data
                    cd = dataParser.textToCountdown msg["text"]

                    if cd?
                        cd.set "title", cd.get "onEndMessage"
                        cd.set "createdFrom", "twitter"
                        cd.set "creatorName", msg.sender["screen_name"]

                        # Insert countdown to the database and add it to the result array.
                        database.insertCountdown cd
                        result.push cd

                        # Log countdown creation and destroy the message.
                        logger.info logTitle, "Generated countdown from message #{msg.id}.", cd.attributes
                        @destroyMessage msg.id
                    else
                        if settings.General.debug
                            logger.info logTitle, "Message #{msg.id} is NOT a countdown request."

                # Callback, if specified.
                callback null, result if callback?

    # Check if there's any scheduled twitter messages to be sent out at this moment.
    # A callback can be passed and will return an error object (if any) and the
    # array with the dispatched messages.
    checkScheduled: (callback) =>
        logTitle = "Twitter.checkScheduled"
        if not auth
            @logNoAuth logTitle, "Will NOT check scheduled twitter updates."
            return

        # Abort if last inbox check was just a few moments ago.
        minDate = moment().subtract "s", settings.Twitter.scheduledCheckInterval
        if @lastScheduledCheck.isAfter minDate
            if settings.General.debug
                logger.warn logTitle, "Scheduled checked just a few moments ago: #{@lastScheduledCheck.format("HH:mm:ss")}. Abort!"
            return

        @lastScheduledCheck = moment()

        # Set date boundaries to (now - scheduledAllowedMinutes) and (now + scheduledCheckInterval / 3).
        lowerDate = moment().subtract("m", settings.Twitter.scheduledAllowedMinutes).format settings.General.isoDateFormat
        upperDate = moment().add("s", settings.Twitter.scheduledCheckInterval / 3).format settings.General.isoDateFormat
        options = {$and: [{targetDate: {$lt: upperDate}}, {targetDate: {$gt: lowerDate}}]}

        if settings.General.debug
            logger.info logTitle, "From #{lowerDate} to #{upperDate}."

        # Get [scheduled twitter updates](scheduledTwitter.html) and post them.
        database.getScheduledTwitter options, (err, data) =>
            if err?
                logger.error logTitle, "Could NOT get scheduled twitter records.", err
            else
                if settings.General.debug
                    logger.info logTitle, "Will dispatch #{data.length} direct messages."

                # Iterate and dispatch messages. If successful, delete the document
                # on the MongoDB database, otherwise log a warning.
                for st in data
                    @sendMessage st.message, st.user, (messageId) =>
                        database.deleteScheduledTwitter st.id if messageId?


    # HELPER METHODS
    # --------------------------------------------------------------------------

    # If `auth` is false and a request is made, log a warning saying it can't proceed.
    logNoAuth: (title, msg) =>
        if settings.General.debug
            logger.warn title, "No auth set!", msg
            return


# Singleton implementation
# --------------------------------------------------------------------------
Twitter.getInstance = ->
    @instance = new Twitter() if not @instance?
    return @instance

module.exports = exports = Twitter.getInstance()